package com.easybbs.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.easybbs.component.RedisComponent;
import com.easybbs.constants.Constants;
import com.easybbs.entity.dto.SysSettingDto;
import com.easybbs.entity.enums.*;
import com.easybbs.entity.po.EmailCode;
import com.easybbs.entity.po.IpRegionInfo;
import com.easybbs.entity.po.UserInfo;
import com.easybbs.entity.po.UserMessage;
import com.easybbs.entity.query.EmailCodeQuery;
import com.easybbs.entity.vo.request.AccountLoginReqVO;
import com.easybbs.entity.vo.request.AccountRegisterReqVO;
import com.easybbs.entity.vo.request.AccountResetPwdReqVO;
import com.easybbs.entity.vo.request.AccountSendEmailCodeReqVO;
import com.easybbs.entity.vo.response.AccountCheckCodeRspVO;
import com.easybbs.entity.vo.response.AccountGetUserInfoRspVO;
import com.easybbs.entity.vo.response.AccountLoginRspVO;
import com.easybbs.exception.BusinessException;
import com.easybbs.service.AccountService;
import com.easybbs.service.EmailCodeService;
import com.easybbs.service.UserInfoService;
import com.easybbs.service.UserMessageService;
import com.easybbs.utils.IpUtils;
import com.easybbs.utils.StringTools;
import com.easybbs.utils.SysCacheUtils;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.base.Captcha;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.bcel.Const;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.net.Inet4Address;
import java.net.SocketException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Optional;

/**
 * @Author: yudaxian
 * @Description: 账户相关业务实现
 * @DateTime: 2024/10/18 10:18
 **/
@Service
@Slf4j
public class AccountServiceImpl implements AccountService {

    @Resource
    private RedisComponent redisComponent;

    @Resource
    private UserInfoService userInfoService;

    @Resource
    private EmailCodeService emailCodeService;

    @Resource
    private UserMessageService userMessageService;

    @Override
    public AccountCheckCodeRspVO checkCode(Integer type) {
        CheckCodeTypeEnum checkCodeTypeEnum = CheckCodeTypeEnum.getByCode(type);
        if (ObjectUtils.isEmpty(checkCodeTypeEnum)) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }

        GifCaptcha gifCaptcha = new GifCaptcha(100, 42);
        try {
            gifCaptcha.setFont(Captcha.FONT_1);
        } catch (Exception e) {
            log.info("验证码字体加载失败", e);
            throw new BusinessException(ResponseCodeEnum.CODE_500);
        }
        String checkCode = gifCaptcha.text();
        String captchaBase64 = gifCaptcha.toBase64();
        log.info("验证码：" + checkCode);

        AccountCheckCodeRspVO accountCheckCodeRspVO = redisComponent.saveCheckCode(checkCode, checkCodeTypeEnum);
        accountCheckCodeRspVO.setCheckCode(captchaBase64);

        return accountCheckCodeRspVO;
    }

    @Override
    public void sendEmailCode(AccountSendEmailCodeReqVO reqVO) {
        CheckCodeTypeEnum checkCodeTypeEnum = null;
        String checkCodeKey = null;
        try {
            Integer codeType = reqVO.getCodeType();
            checkCodeTypeEnum = CheckCodeTypeEnum.getByCode(codeType);
            if (ObjectUtils.isEmpty(checkCodeTypeEnum) || !checkCodeTypeEnum.equals(CheckCodeTypeEnum.EMAIL)) {
                throw new BusinessException(ResponseCodeEnum.CODE_600);
            }
            // 判断邮箱是否已经被注册了
            String email = reqVO.getEmail();
            UserInfo dbUserInfoByEmail = userInfoService.getUserInfoByEmail(email);
            if (!ObjectUtils.isEmpty(dbUserInfoByEmail)) {
                throw new BusinessException(ResponseCodeEnum.CODE_602);
            }
            // 判断验证码是否正确
            String checkCode = reqVO.getCheckCode();
            checkCodeKey = reqVO.getCheckCodeKey();

            // 验证码是否正确
            Boolean checkCodeIsCorrect = redisComponent.checkCheckCode(checkCode, checkCodeKey, checkCodeTypeEnum);
            if (!checkCodeIsCorrect) {
                throw new BusinessException(ResponseCodeEnum.CODE_603);
            }

            // 生成邮箱验证码5位数
            String sendEmailCode = StringTools.getRandomNumber(Constants.RANDOM_EMAIL_CHECKCODE);
            // 验证码正确，发送邮件验证码
            emailCodeService.sendEmailCode(email, sendEmailCode, reqVO.getEmailCodeType());

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(e.getMessage(), e);
        } finally {
            // 删除redis中的验证码
            redisComponent.deleteCheckCode(checkCodeKey, checkCodeTypeEnum);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(AccountRegisterReqVO reqVO) {
        try {
            // 查看邮箱是否已经被注册
            String email = reqVO.getEmail();
            UserInfo dbUserInfo = userInfoService.getUserInfoByEmail(email);
            if (!ObjectUtils.isEmpty(dbUserInfo)) {
                throw new BusinessException(ResponseCodeEnum.CODE_602);
            }

            // 昵称是否已经被注册
            String nickName = reqVO.getNickName();
            dbUserInfo = userInfoService.getUserInfoByNickName(nickName);
            if (!ObjectUtils.isEmpty(dbUserInfo)) {
                throw new BusinessException(ResponseCodeEnum.CODE_606);
            }

            // 邮箱验证码是否正确
            String emailCode = reqVO.getEmailCode();
            EmailCode dbEmailCode = emailCodeService.getEmailCodeByEmailAndCode(email, emailCode);
            if (ObjectUtils.isEmpty(dbEmailCode) || !emailCode.equals(dbEmailCode.getCode()) || dbEmailCode.getStatus().equals(EmailCodeStatusEnum.USED.getCode())) {
                throw new BusinessException(ResponseCodeEnum.CODE_603);
            }

            // 验证码是否过期（超过15分钟）
            LocalDateTime plusMinutes = LocalDateTime.now().plusMinutes(15);
            LocalDateTime createTime = dbEmailCode.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
            if (createTime.isAfter(plusMinutes)) {
                throw new BusinessException(ResponseCodeEnum.CODE_604);
            }

            // 图片验证码是否正确
            String checkCode = reqVO.getCheckCode();
            String checkCodeKey = reqVO.getCheckCodeKey();
            Boolean checkCheckCode = redisComponent.checkCheckCode(checkCode, checkCodeKey, CheckCodeTypeEnum.LOGIN_REGISTER);
            if (!checkCheckCode) {
                throw new BusinessException(ResponseCodeEnum.CODE_605);
            }

            // 注册用户信息，加密密码
            UserInfo userInfo = UserInfo.builder()
                    .userId(StringTools.getRandomNumber(Constants.RANDOM_USER_ID))
                    .email(email)
                    .nickName(reqVO.getNickName())
                    .password(StringTools.encryptPassword(reqVO.getPassword()))
                    .joinTime(new Date())
                    .status(UserInfoStatusEnum.NORMAL.getCode())
                    .role(UserRoleEnum.USER.getRoleName())
                    .build();
            userInfoService.add(userInfo);

            SysSettingDto sysSetting = SysCacheUtils.getSysSetting();
            // 发送系统消息
            UserMessage userMessage = new UserMessage();
            userMessage.setReceivedUserId(userInfo.getUserId());
            userMessage.setMessageType(MessageTypeEnum.SYS.getType());
            userMessage.setStatus(MessageStatusEnum.NO_READ.getStatus());
            userMessage.setMessageContent(sysSetting.getRegisterSetting().getRegisterWelcomeInfo());
            userMessage.setCreateTime(new Date());
            userMessageService.add(userMessage);

            // 更新用户积分
            userInfoService.updateUserInfoIntegral(userInfo.getUserId(), UserIntegralOperTypeEnum.REGISTER, UserIntegralChangeTypeEnum.ADD, Constants.USER_DEFAULT_INTEGRAL);

            // 注册成功，删除redis中的验证码，以及修改数据库中的邮箱验证码状态
            dbEmailCode.setStatus(EmailCodeStatusEnum.USED.getCode());
            EmailCodeQuery emailCodeQuery = new EmailCodeQuery();
            emailCodeQuery.setStatus(EmailCodeStatusEnum.UN_USED.getCode());
            emailCodeQuery.setEmail(email);
            emailCodeQuery.setCode(emailCode);
            emailCodeService.updateByParam(dbEmailCode, emailCodeQuery);
        } catch (BusinessException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(e.getMessage(), e);
        } finally {
            // 删除redis中的验证码
            redisComponent.deleteCheckCode(reqVO.getCheckCodeKey(), CheckCodeTypeEnum.LOGIN_REGISTER);
        }
    }

    @Override
    public AccountLoginRspVO login(AccountLoginReqVO reqVO) {
        AccountLoginRspVO rspVO = null;
        try {
            // 判断验证码是否正确
            String checkCode = reqVO.getCheckCode();
            String checkCodeKey = reqVO.getCheckCodeKey();
            Boolean checkCheckCode = redisComponent.checkCheckCode(checkCode, checkCodeKey, CheckCodeTypeEnum.LOGIN_REGISTER);
            if (!checkCheckCode) {
                throw new BusinessException(ResponseCodeEnum.CODE_605);
            }

            // 判断用户名或密码是否正确
            String email = reqVO.getEmail();
            UserInfo dbUserInfo = userInfoService.getUserInfoByEmail(email);
            if (ObjectUtils.isEmpty(dbUserInfo)) {
                throw new BusinessException(ResponseCodeEnum.CODE_607);
            }

            // 未加密的密码
            String password = reqVO.getPassword();
            // bcrypt加密的密码
            String dbPassword = dbUserInfo.getPassword();

            if (!StringTools.decryptPassword(password, dbPassword)) {
                throw new BusinessException(ResponseCodeEnum.CODE_608);
            }
            // 登录成功，生成token
            StpUtil.login(dbUserInfo.getUserId());
            String tokenValue = StpUtil.getTokenValue();
            rspVO = AccountLoginRspVO.builder()
                    .userId(dbUserInfo.getUserId())
                    .nickName(dbUserInfo.getNickName())
                    .email(dbUserInfo.getEmail())
                    .token(tokenValue)
                    .role(dbUserInfo.getRole())
                    .build();

            // 更新数据库中的登录时间，ip
            try {
                String ip4Address = IpUtils.getLocalIp4Address();
                dbUserInfo.setLastLoginTime(new Date());
                dbUserInfo.setLastLoginIp(ip4Address);
                IpRegionInfo locationByIp = IpUtils.getLocationByIp(ip4Address);
                String province = locationByIp.getProvince();
                if (StringTools.isEmpty(province)) {
                    province = "未知";
                }
                dbUserInfo.setLastLoginIpAddress(province);
                userInfoService.updateUserInfoByUserId(dbUserInfo,
                        dbUserInfo.getUserId());
            } catch (SocketException e) {
                log.error(e.getMessage(), e);
            }

        } catch (BusinessException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(e.getMessage(), e);
        } finally {
            // 删除redis中的验证码
            redisComponent.deleteCheckCode(reqVO.getCheckCodeKey(), CheckCodeTypeEnum.LOGIN_REGISTER);
        }
        return rspVO;
    }

    @Override
    public UserInfo getOnlyLoginUserInfo() {
        /**
         * 获取当前登录用户信息，如果未登录，则抛异常
         */
        String loginId = (String) StpUtil.getLoginId();
        UserInfo userInfoByUserId = userInfoService.getUserInfoByUserId(loginId);
        return userInfoByUserId;
    }

    @Override
    public UserInfo getLoginOrNotUserInfo() {
        /**
         * 获取当前登录用户信息，如果未登录，则返回默认值 null
         */
        String loginId = StpUtil.getLoginId("0");
        UserInfo userInfoByUserId = userInfoService.getUserInfoByUserId(loginId);
        return userInfoByUserId;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resetPwd(AccountResetPwdReqVO reqVO) {
        // 请求参数校验
        EmailCodeTypeEnum enumByCode = EmailCodeTypeEnum.getEnumByCode(Integer.valueOf(reqVO.getEmailCodeType()));
        if (enumByCode == null) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        UserInfo loginUserInfo = this.getOnlyLoginUserInfo();
        if (!loginUserInfo.getEmail().equals(reqVO.getEmail())) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }

        // 验证码是否正确
        EmailCode emailCodeByEmailAndCode = emailCodeService.getEmailCodeByEmailAndCode(reqVO.getEmail(), reqVO.getEmailCode());
        if (ObjectUtils.isEmpty(emailCodeByEmailAndCode) || !reqVO.getEmailCode().equals(emailCodeByEmailAndCode.getCode()) || emailCodeByEmailAndCode.getStatus().equals(EmailCodeStatusEnum.USED.getCode())) {
            throw new BusinessException(ResponseCodeEnum.CODE_603);
        }

        // 验证码是否过期（超过15分钟）
        LocalDateTime plusMinutes = LocalDateTime.now().plusMinutes(15);
        LocalDateTime createTime = emailCodeByEmailAndCode.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
        if (createTime.isAfter(plusMinutes)) {
            throw new BusinessException(ResponseCodeEnum.CODE_604);
        }

        // 更新用户密码
        loginUserInfo.setPassword(StringTools.encryptPassword(Constants.USER_DEFAULT_PASSWORD));
        userInfoService.updateUserInfoByUserId(loginUserInfo, loginUserInfo.getUserId());

        // 更新数据库邮箱验证码状态
        emailCodeByEmailAndCode.setStatus(EmailCodeStatusEnum.USED.getCode());
        emailCodeService.updateEmailCodeByEmailAndCode(emailCodeByEmailAndCode, reqVO.getEmail(), reqVO.getEmailCode());

        // TODO 发送密码重置成功的邮件，告知用户默认密码是多少
    }
}
